package com.bestcem.xm.component.security.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.bestcem.xm.common.core.constant.AppRequestErrorCode;
import com.bestcem.xm.common.core.constant.AppResourceErrorCode;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.domain.web.XmViewResultJson;
import com.bestcem.xm.common.core.enums.AuthErrorCodeEnum;
import com.bestcem.xm.component.security.annotation.AuthIgnore;
import com.bestcem.xm.component.security.annotation.XmAuth;
import com.bestcem.xm.component.security.context.SecurityContextHolder;
import com.bestcem.xm.component.security.dto.AuthenticationDTO;
import com.bestcem.xm.component.security.dto.TokenDTO;
import com.bestcem.xm.component.security.properties.XmSecurityProperties;
import com.bestcem.xm.component.security.retry.CheckAuthenticationRetry;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.Optional;

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    private XmSecurityProperties properties;

    private CheckAuthenticationRetry checkAuthenticationRetry;

    public void setProperties(XmSecurityProperties properties) {
        this.properties = properties;
    }

    public void setCheckAuthenticationRetry(CheckAuthenticationRetry checkAuthenticationRetry) {
        this.checkAuthenticationRetry = checkAuthenticationRetry;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler)
            throws Exception {
        String uri = request.getRequestURI();
        if (log.isInfoEnabled()) {
            log.info("[Auth] 访问链接: {} : {}", request.getServerName(), uri);
        }

        if (!properties.getCheckAuth()) {
            // 本地调试的使用全局假数据
            SecurityContextHolder.setUserId(properties.getUserId());
            SecurityContextHolder.setOrgId(properties.getOrgId());
            SecurityContextHolder.setToken(toTokenDTO(properties));
            SecurityContextHolder.setOrgCode(properties.getOrgCode());
            return true;
        }
        // 是否需要校验权限
        Boolean xmAuthAuthentication = null;
        // 处理无需检测的接口
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            AuthIgnore annotation = method.getMethodAnnotation(AuthIgnore.class);
            if (Objects.isNull(annotation)) {
                annotation = method.getBeanType().getAnnotation(AuthIgnore.class);
            }
            // 如果有AuthIgnore注解，则无需验证登录
            if (Objects.nonNull(annotation)) {
                return true;
            }

            // 获取是否有XmAuth注解, 有的话设置是否需要校验权限
            XmAuth xmAuthAnnotation = method.getMethodAnnotation(XmAuth.class);
            if (Objects.isNull(xmAuthAnnotation)) {
                xmAuthAnnotation = method.getBeanType().getAnnotation(XmAuth.class);
            }
            if (Objects.nonNull(xmAuthAnnotation)) {
                xmAuthAuthentication = xmAuthAnnotation.authentication();
            }
        }

        XmViewResultJson<Boolean> result = this.checkAuth(request, xmAuthAuthentication);
        if (!result.isSuccess()) {
            printlnError(response, result.getCode(), result.getMsg());
            return false;
        } else if (!result.getData()) {
            printlnError(response, null, null);
            return false;
        }
        return true;
    }

    @Override
    public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, Exception ex) {
        SecurityContextHolder.remove();
    }

    private XmViewResultJson<Boolean> checkAuth(HttpServletRequest request, Boolean authentication) {

        String uri = StringUtils.trimToEmpty(request.getRequestURI());
        String method = request.getMethod().toUpperCase();
        String authorization = request.getHeader("authorization");
        if (log.isDebugEnabled()) {
            log.debug("auth, method: {}, uri: {} authorization: {}", method, uri, authorization);
        }

        // 处理Authorization
        if (StringUtils.isBlank(authorization)) {
            log.error("auth failed, no token, method: {}, uri: {} authorization: {}", method, uri, authorization);
            return XmViewResultJson.fail(AppRequestErrorCode.TOKEN_LOST, "No Token");
        }
        String[] authorizationParts = authorization.split(" ");
        if (authorizationParts.length != 2) {
            log.error("auth failed, invalid token, method: {}, uri: {} authorization: {}", method, uri, authorization);
            return XmViewResultJson.fail(AppRequestErrorCode.INVALID_TOKEN, "Invalid Token");
        }
        String tokenString = StringUtils.trimToEmpty(authorizationParts[1]);

        try {
            authentication = Optional.ofNullable(authentication).orElse(Boolean.TRUE);

            ServiceResult<AuthenticationDTO> result = checkAuthenticationRetry.callApi(tokenString, authentication, uri, method);
            if (!result.isSuccess()) {
                if (String.valueOf(AuthErrorCodeEnum.EXPIRE_TOKEN.getCode()).equals(result.getErrorCode())) {
                    return XmViewResultJson.fail(AuthErrorCodeEnum.EXPIRE_TOKEN);
                } else if (String.valueOf(AppRequestErrorCode.INVALID_TOKEN).equals(result.getErrorCode())) {
                    return XmViewResultJson.fail(AppRequestErrorCode.INVALID_TOKEN, "Invalid Token");
                }
                log.error("auth failed, no permission, method: {}, uri: {}, authorization: {}, authentication: {}, result: {}",
                        method, uri, tokenString, authentication, result);
                return XmViewResultJson.fail(AppResourceErrorCode.PERMISSION_DENY, "No Permission");
            }
            AuthenticationDTO data = result.getData();
            TokenDTO token = data.getToken();

            SecurityContextHolder.setUserId(token.getUserId());
            SecurityContextHolder.setOrgId(token.getOrgId());
            SecurityContextHolder.setOrgCode(properties.getOrgCode());
            SecurityContextHolder.setToken(token);
            return XmViewResultJson.success(authentication ? data.getPermission() : Boolean.TRUE);
        } catch (Exception e) {
            log.error("auth failed, unknown error, method: {}, uri: {}, authorization: {}",
                    method, uri, tokenString, e);
            return XmViewResultJson.fail(AppResourceErrorCode.PERMISSION_DENY, "No Permission");
        }

    }


    private void printlnError(HttpServletResponse response, Integer code, String msg) throws IOException {
        JSONObject errorJSON = new JSONObject();
        if (code != null) {
            errorJSON.put("code", code);
            errorJSON.put("msg", msg);
        } else {
            errorJSON.put("code", AppResourceErrorCode.PERMISSION_DENY);
            errorJSON.put("msg", "Forbidden: no permission");
        }

        response.setHeader("content-type", "application/json;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        PrintWriter pw = response.getWriter();
        pw.println(errorJSON.toJSONString());
    }


    private TokenDTO toTokenDTO(XmSecurityProperties properties) {
        TokenDTO dto = new TokenDTO();
        dto.setAvatar(properties.getAvatar());
        //dto.setOmRole(properties.getOmRole());
        dto.setOrgCode(properties.getOrgCode());
        dto.setOrgId(properties.getOrgId());
        dto.setRealName(properties.getRealName());
        dto.setSuperRole(properties.getSuperRole());
        dto.setUserId(properties.getUserId());
        dto.setIsSenior(properties.getIsSenior());
        return dto;
    }
}
